using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;

namespace SystemTimeCalibration
{
    internal class Program
    {
        static List<Thread> threads = new List<Thread>();
        public struct ThreadTime
        {
            public string server;
            public Thread thread;
        }
        static bool state = false;
        static string[] NTPServer = {
            "ntp.aliyun.com",
            "time.edu.cn",
            "cn.pool.ntp.org",
            "time1.cloud.tencent.com",
            "time1.google.com",
            "ntp.ntsc.ac.cn",
            "ntp7.aliyun.com",
            "time5.cloud.tencent.com",
            "time4.google.com",
            "time.apple.com",
            "time.cloudflare.com",
            "ntp.neu.edu.cn",
            "ntp1.nim.ac.cn"
        };
        static void Main(string[] args)
        {
            Console.WriteLine("By: WiMi");
            Console.WriteLine("多线程获取NTP服务器时间");
            Start();
            new Thread(new ThreadStart(Detection)).Start();
        }

        static void Start()
        {
            foreach (string item in NTPServer)
            {
                Thread th = new Thread(new ParameterizedThreadStart(GetNetworkTime));
                threads.Add(th);
                ThreadTime t = new ThreadTime() { server = item,thread = th};
                th.Start(t);
            }
        }
        internal struct SYSTEMTIME
        {
            public ushort wYear;
            public ushort wMonth;
            public ushort wDayOfWeek;
            public ushort wDay;
            public ushort wHour;
            public ushort wMinute;
            public ushort wSecond;
            public ushort wMilliseconds;

            /// <summary>
            /// 从System.DateTime转换。
            /// </summary>
            /// <param name="time">System.DateTime类型的时间。</param>
            public void FromDateTime(DateTime time)
            {
                wYear = (ushort)time.Year;
                wMonth = (ushort)time.Month;
                wDayOfWeek = (ushort)time.DayOfWeek;
                wDay = (ushort)time.Day;
                wHour = (ushort)time.Hour;
                wMinute = (ushort)time.Minute;
                wSecond = (ushort)time.Second;
                wMilliseconds = (ushort)time.Millisecond;
            }
            /// <summary>
            /// 转换为System.DateTime类型。
            /// </summary>
            /// <returns></returns>
            public DateTime ToDateTime()
            {
                return new DateTime(wYear, wMonth, wDay, wHour, wMinute, wSecond, wMilliseconds);
            }
        }
        internal class Win32API
        {
            [DllImport("Kernel32.dll")]
            public static extern bool SetLocalTime(ref SYSTEMTIME Time);
            [DllImport("Kernel32.dll")]
            public static extern void GetLocalTime(ref SYSTEMTIME Time);
        }

        public class SystemHelper
        {
            public static void SetLocalMachineTime(DateTime dt,Thread thread)
            {
                if (state)
                {
                    return;
                }
                SYSTEMTIME st = new SYSTEMTIME();
                st.FromDateTime(dt);
                Win32API.SetLocalTime(ref st);
                state = true;
            }
        }

        /// <summary>
        /// 获取NTP时间
        /// </summary>
        /// <returns></returns>
        static void GetNetworkTime(object ntpServer)
        {
            ThreadTime threadtime = (ThreadTime)ntpServer;
            string server;
            Thread thread;
            if (threadtime.server != null && threadtime.thread != null)
            {
                server = threadtime.server;
                thread = threadtime.thread;
            }
            else
            {
                return;
            }

            var ntpData = new byte[48];
            ntpData[0] = 0x1B;
            IPAddress[] addresses;
            IPEndPoint ipEndPoint;
            Socket socket;
            try
            {
                addresses = Dns.GetHostEntry(server).AddressList;
                ipEndPoint = new IPEndPoint(addresses[0], 123);
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                socket.Connect(ipEndPoint);
                socket.ReceiveTimeout = 3000;
                socket.Send(ntpData);
                socket.Receive(ntpData);
                socket.Close();
            }
            catch (Exception log)
            {
                Console.WriteLine(server +"：" + log.Message);
                threads.Remove(thread);
                thread.Abort();
                return;
            }
            const byte serverReplyTime = 40;
            ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
            ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
            intPart = SwapEndianness(intPart);
            fractPart = SwapEndianness(fractPart);
            var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
            var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
            Console.WriteLine(server + "：" + networkDateTime.ToLocalTime());
            SystemHelper.SetLocalMachineTime(networkDateTime.ToLocalTime(), thread);
        }

        static uint SwapEndianness(ulong x)
        {
            return (uint)(((x & 0x000000ff) << 24) +
            ((x & 0x0000ff00) << 8) +
            ((x & 0x00ff0000) >> 8) +
            ((x & 0xff000000) >> 24));
        }

        static void Detection()
        {
            while (true) {
                if (state)
                {
                    foreach (var item in threads.ToArray())
                    {
                        if (item.IsAlive)
                        {
                            item.Abort();
                            threads.Remove(item);
                        }
                    }
                    Console.WriteLine("时间同步完成，3秒后自动退出");
                    Thread.Sleep(3000);
                    return;
                }
                else if(threads.Count == 0)
                {
                    Start();
                }
            }
        }
    }
}
